package com.dhcc.core.config;

import com.dhcc.core.config.properties.SsoProperties;
import com.dhcc.core.config.properties.SysProperties;
import com.dhcc.core.framework.security.OnlineSessionFactory;
import com.dhcc.core.framework.security.Pac4jShiroRealm;
import com.dhcc.core.framework.security.filter.KickoutSessionControlFilter;
import com.dhcc.core.framework.security.filter.Pac4jCallbackFilter;
import com.dhcc.core.framework.security.filter.Pac4jLogoutFilter;
import com.dhcc.core.framework.security.filter.Pac4jSecurityFilter;
import com.dhcc.core.framework.util.CommonUtil;
import io.buji.pac4j.subject.Pac4jSubjectFactory;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.session.mgt.SessionFactory;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.MemorySessionDAO;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.ShiroHttpSession;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.pac4j.core.config.Config;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * shiro权限管理的配置
 *
 * @ClassName: ShiroConfig
 * @Description: 描述
 * @author: cyf
 * @date: 2018年1月2日 上午9:42:39
 */
@Configuration
public class ShiroConfig {

    /**
     * 使用 pac4j 的 subjectFactory
     *
     * @return
     */
    @Bean
    public Pac4jSubjectFactory subjectFactory() {
        return new Pac4jSubjectFactory();
    }

    /**
     * 项目自定义的Realm
     */
    @Bean
    public Pac4jShiroRealm pac4jShiroRealm() {
        return new Pac4jShiroRealm();
    }

    /**
     * 安全管理器
     */
    @Bean
    public DefaultWebSecurityManager securityManager(Pac4jSubjectFactory subjectFactory,
            CookieRememberMeManager rememberMeManager, CacheManager cacheShiroManager, SessionManager sessionManager,
            Pac4jShiroRealm pac4jShiroRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRememberMeManager(rememberMeManager);
        securityManager.setSessionManager(sessionManager);
        securityManager.setRealm(pac4jShiroRealm);
        securityManager.setCacheManager(cacheShiroManager);
        securityManager.setSubjectFactory(subjectFactory);
        return securityManager;
    }

    /**
     * session管理器--单机
     */
    @Bean
    @ConditionalOnProperty(prefix = "sys", name = "sessionRedis", havingValue = "false")
    public SessionDAO memorySessionDAO() {
        // session管理器--单机
        return new MemorySessionDAO();
    }

    /**
     * 自定义sessionFactory会话
     */
    @Bean
    public OnlineSessionFactory sessionFactory() {
        OnlineSessionFactory sessionFactory = new OnlineSessionFactory();
        return sessionFactory;
    }

    /**
     * session管理器
     */
    @Bean
    public DefaultWebSessionManager webSessionManager(CacheManager cacheShiroManager, SessionDAO sessionDAO,
            SysProperties sysProperties, SessionFactory sessionFactory) {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setCacheManager(cacheShiroManager);
        // sessionDAO
        sessionManager.setSessionDAO(sessionDAO);
        sessionManager.setSessionFactory(sessionFactory);
        sessionManager.setSessionValidationInterval(sysProperties.getSessionValidationInterval() * 1000);
        sessionManager.setGlobalSessionTimeout(sysProperties.getSessionInvalidateTime() * 1000);
        sessionManager.setDeleteInvalidSessions(true);
        sessionManager.setSessionValidationSchedulerEnabled(true);
        // 去掉 JSESSIONID
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        Cookie cookie = new SimpleCookie(ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
        cookie.setName(sysProperties.getSessionCookieName());
        cookie.setHttpOnly(true);
        cookie.setMaxAge(-1);// 表示浏览器关闭时失效此Cookie；
        if (!SsoProperties.me().hasForm()) { //如果sso类型不为form
            cookie.setSameSite(Cookie.SameSiteOptions.NONE);
        }
        sessionManager.setSessionIdCookie(cookie);
        return sessionManager;
    }

    /**
     * rememberMe管理器, cipherKey生成见{@code Base64Test.java}
     */
    @Bean
    public CookieRememberMeManager rememberMeManager(SimpleCookie rememberMeCookie) {
        CookieRememberMeManager manager = new CookieRememberMeManager();
        manager.setCipherKey(Base64.decode("qe8U8yv5eg3dCOgyluUnuA=="));
        manager.setCookie(rememberMeCookie);
        return manager;
    }

    /**
     * 记住密码Cookie
     */
    @Bean
    public SimpleCookie rememberMeCookie(SysProperties sysProperties) {
        SimpleCookie simpleCookie = new SimpleCookie(sysProperties.getRememberMeCookieName());
        simpleCookie.setHttpOnly(true);
        simpleCookie.setMaxAge(7 * 24 * 60 * 60);// 7天
        return simpleCookie;
    }

    /**
     * Shiro的过滤器链
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager, SysProperties sysProperties, SsoProperties ssoProperties,Config config) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        // 增加自定义过滤
        Map<String, Filter> filters = new HashMap<>();
        //cas 资源认证拦截器
        Pac4jSecurityFilter securityFilter = new Pac4jSecurityFilter();
        securityFilter.setConfig(config);
        securityFilter.setClients(ssoProperties.getTypesStr());
        filters.put("backend", securityFilter);
        filters.put("front", securityFilter);
        // 限制同一帐号同时在线的个数。
        filters.put("kickout", kickoutSessionControlFilter(sysProperties, securityManager.getCacheManager(),
                securityManager.getSessionManager()));
        // 注销成功，则跳转到指定页面
        Pac4jLogoutFilter backend_logout = new Pac4jLogoutFilter();
        backend_logout.setConfig(config);
        backend_logout.setCentralLogout(true);
        backend_logout.setLocalLogout(true);
        backend_logout.setIsFront(false);
        //添加logout后  跳转到指定url  url的匹配规则  默认为 /.*;
        backend_logout.setLogoutUrlPattern(".*");
        backend_logout.setDefaultUrl("/callback?client_name=" + ssoProperties.getDefaultType());
        filters.put("backend_logout", backend_logout);
        Pac4jLogoutFilter front_logout = new Pac4jLogoutFilter();
        front_logout.setConfig(config);
        front_logout.setCentralLogout(true);
        front_logout.setLocalLogout(true);
        front_logout.setIsFront(true);
        //添加logout后  跳转到指定url  url的匹配规则  默认为 /.*;
        front_logout.setLogoutUrlPattern(".*");
        front_logout.setDefaultUrl("/callback?client_name=" + ssoProperties.getDefaultType());
        filters.put("front_logout", front_logout);
       //cas 认证后回调拦截器
        Pac4jCallbackFilter callback = new Pac4jCallbackFilter();
        callback.setConfig(config);
        callback.setDefaultUrl("/");
        filters.put("callback", callback);

        shiroFilter.setFilters(filters);
        /**
         * 没有权限跳转的url
         */
        shiroFilter.setUnauthorizedUrl("/error");
        /**
         * 配置shiro拦截器链
         *
         * anon 不需要认证 authc 需要认证 user 验证通过或RememberMe登录的都可以
         *
         */
        Map<String, String> hashMap = new LinkedHashMap<String, String>();
        // swagger配置
        hashMap.put("/v2/api-docs", "authc");
        hashMap.put("/favicon.ico", "anon");
        hashMap.put("/B6A7LTPFNA.txt", "anon");
        hashMap.put("/v2/api-docs-ext", "authc");
        hashMap.put("/doc.html", "anon");
        hashMap.put("/public/**", "anon");
        hashMap.put("/webjars/**", "anon");
        hashMap.put("/swagger-resources", "authc");
        hashMap.put("/configuration/**", "anon");
        hashMap.put("/static/**", "anon");
        hashMap.put("/mediway/**", "anon");
        hashMap.put("/sds/**", "anon");
        hashMap.put("/sdc/sdcqcformshow/**", "anon");
        hashMap.put("/error", "anon");
        hashMap.put("/kaptcha", "anon");
        hashMap.put("/file/download/**", "anon");
        hashMap.put("/file/image/**", "anon");
        hashMap.put("/file/avatar/**", "anon");
        hashMap.put("/im/**", "anon");
        hashMap.put(sysProperties.getWsPath() + "/**", "anon");
        hashMap.put(sysProperties.getApiPath() + "/**", "anon");
        
        hashMap.put("/passwordReset", "anon");
        hashMap.put("/resetPass", "anon");
        hashMap.put("/getVcode", "anon");
        hashMap.put("/verifyCode", "anon");
        hashMap.put("/callback", "callback");
        hashMap.put("/wechatLogin", "anon");
        hashMap.put("/scanLogin", "anon");
        hashMap.put("/isLogin", "anon");
        hashMap.put("/bpm/process/inst/trace/**", "anon");
        hashMap.put("/bpm/viewer/procInstId/**", "anon");
        // 这是一个很蛋疼的问题，我就不多说了(范围大的放后边)
        if (CommonUtil.isEmpty(sysProperties.getBackendPath())) {
            hashMap.put(sysProperties.getFrontPath() + "/logout", "front_logout");
            hashMap.put(sysProperties.getFrontAuthPath() + "/**", "front");
            hashMap.put(sysProperties.getFrontPath() + "/**", "anon");
            if(!sysProperties.getBackendPath().contains("api")){
            	hashMap.put(sysProperties.getBackendPath() + "/login", "anon");
                hashMap.put(sysProperties.getBackendPath() + "/logout", "backend_logout");
                hashMap.put(sysProperties.getBackendPath() + "/**", "backend,kickout");
            }
            
        } else {
        	if(!sysProperties.getBackendPath().contains("api")){
        		hashMap.put(sysProperties.getBackendPath() + "/logout", "backend_logout");
                hashMap.put(sysProperties.getBackendPath() + "/**", "backend,kickout");
                hashMap.put(sysProperties.getBackendPath() + "/login", "anon");
        	}
            hashMap.put(sysProperties.getFrontPath() + "/logout", "front_logout");
            hashMap.put(sysProperties.getFrontAuthPath() + "/**", "front");
            hashMap.put(sysProperties.getFrontPath() + "/**", "anon");
        }
        shiroFilter.setFilterChainDefinitionMap(hashMap);
        return shiroFilter;
    }

    /**
     * 在方法中 注入 securityManager,进行代理控制
     */
    @Bean
    public MethodInvokingFactoryBean methodInvokingFactoryBean(DefaultWebSecurityManager securityManager) {
        MethodInvokingFactoryBean bean = new MethodInvokingFactoryBean();
        bean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
        bean.setArguments(securityManager);
        return bean;
    }

    /**
     * 保证实现了Shiro内部lifecycle函数的bean执行
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 启用shrio授权注解拦截方式，AOP式方法级权限检查
     */
    @Bean
    @DependsOn(value = "lifecycleBeanPostProcessor") // 依赖其他bean的初始化
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        return new DefaultAdvisorAutoProxyCreator();
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
            DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * 限制同一账号登录同时登录人数控制
     *
     * @return
     */
    public KickoutSessionControlFilter kickoutSessionControlFilter(SysProperties sysProperties,
            CacheManager cacheShiroManager, SessionManager sessionManager) {
        KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter();
        // 使用cacheManager获取相应的cache来缓存用户登录的会话；用于保存用户—会话之间的关系的；
        // 这里我们还是用之前shiro使用的redisManager()实现的cacheManager()缓存管理
        // 也可以重新另写一个，重新配置缓存时间之类的自定义缓存属性
        kickoutSessionControlFilter.setCacheManager(cacheShiroManager);
        // 用于根据会话ID，获取会话进行踢出操作的；
        kickoutSessionControlFilter.setSessionManager(sessionManager);
        // 是否踢出后来登录的，默认是false；即后者登录的用户踢出前者登录的用户；踢出顺序。
        kickoutSessionControlFilter.setKickoutAfter(false);
        // 同一个用户最大的会话数，默认1；比如2的意思是同一个用户允许最多同时两个人登录；
        kickoutSessionControlFilter.setMaxSession(sysProperties.getMaxSession());
        return kickoutSessionControlFilter;
    }
}
